/* vccd/main.c 
 * 
 * This file is part of vccd. 
 * 
 * vccd is free software: you can redistribute it and/or modify 
 * it under the terms of the GNU General Public License as published by 
 * the Free Software Foundation, either version 3 of the License, or 
 * (at your option) any later version. 
 * 
 * vccd is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
 * GNU General Public License for more details. 
 * 
 * You should have received a copy of the GNU General Public License 
 * along with vccd. If not, see <https://www.gnu.org/licenses/>
 */ 




#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>

#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <poll.h>
#include <signal.h>

#include <vcc/vcc.h>
#include <vcc/vccd.h>
#include <vcc/version.h>


/* well, it's not a really daemon because we didn't do things about 
 * sessions or pgrps, and didn't close stdout stderr stdin etc.
 *
 * but that only makes things more difficult and hard to understand 
 * that's why we just fork() and exit() */

/* this is the shortest name i think. it stores the fd of the server 
 * socket */

int 			fd;
int 			next_fd;
int 			infofd = 1;

#define POLLFDS 	1024
struct pollfd 		*fds;

int users_init(void);

static int usage(void) {
	fprintf(stderr, "Usage: vccd (no argument)\n");

	return 1;
}

int becomes_daemon(void) {
	int pid;

	if ((pid = fork())) 
		/* then prompt was there again */
		exit(0);

	umask(0);
	chdir("/");

	close(0);

	if (unlikely(open("/var/log/vccd.log", O_WRONLY | O_CREAT | O_APPEND | O_SYNC, 0644) < 0)) {
		perror("open(/var/run/vccd.log)");

		exit(1);
	}

	close(1);
	close(2);

	(void) dup(0);
	(void) dup(0);

	if (unlikely((infofd = open("/var/log/vccd-info.log", O_WRONLY | O_CREAT | O_APPEND | O_SYNC, 0644)) < 0)) {
		perror("open(/var/run/vccd-info.log)");

		exit(1);
	}

	setsid();

	if (unlikely((fd = open("/var/run/vccd.pid", O_WRONLY | O_CREAT | O_TRUNC, 0644)) < 0)) {
		perror("open(/var/run/vccd.pid)");

		exit(1);
	}

	dprintf(fd, "%d\n", getpid());
	close(fd);

	/* fd 3 is now /var/run/vccd.pid */
	vcc_log(VCCD_VERSION " started. \n");

	return 0;
}

static int init_socket(void) {
	struct sockaddr_in addr;
	int i;

	if (unlikely((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)) {
		perror("socket()");

		exit(1);
	}

	memset(&addr, 0, sizeof(struct sockaddr_in));

	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = htonl(INADDR_ANY);
	addr.sin_port = htons(VCC_PORT);

	if (unlikely(bind(fd, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) < 0)) {
		perror("bind()");

		exit(1);
	}

	if (unlikely(listen(fd, SOMAXCONN) < 0)) {
		perror("listen()");

		exit(1);
	}

	if (!(fds = amalloc(sizeof(struct pollfd) * POLLFDS))) {
		vcc_log("*** malloc() failed\n");

		exit(1);
	}

	memset(fds, 0, sizeof(struct pollfd) * POLLFDS);

	for (i = 0; i < POLLFDS; i++) 
		fds[i].fd = -1;

	fds[0].fd = fd;
	fds[0].events = POLLIN;

	next_fd = 1;

	return fd;
}

void sigpipe_handler(int nr) {
	(void) nr;

	vcc_log("SIGPIPE caused. \n");
}

static int setup_signals(void) {
	struct sigaction act;

	memset(&act, 0, sizeof(struct sigaction));
	act.sa_handler = sigpipe_handler;
	
	sigaction(SIGPIPE, &act, NULL);

	return 0;
}


/* a bad programme can joke us by a wrong size member in the struct 
 * vcc_relay_header. he makes it large, but never writes after. then 
 * we'll wait in the loop. 
 *
 * we can use a 'sending' member in the struct connection and that was 
 * what we tried to do. but againly, do that later. :-( */

int read_until(int fd, void *p, int until) {
	int did_read = 0;
	int n;

	while ((did_read += n = read(fd, p, until)) < until) {
		if (!n) 
			return 0;

		p += n;
	}
	
	return until;
}


int main(int argc, const char **argv) {
	__uint32_t 		size;
	int 			usr_fd, nfds_total, nfds_ready, i;
	void 			*buf;

	struct sockaddr_in 	usr_addr;

	(void) argv;

	if (unlikely(argc != 1)) 
		return usage();

	becomes_daemon();
	init_socket();
	users_init();

	setup_signals();

	nfds_total = 1;

	if (unlikely(!(buf = amalloc(REQ_SIZE)))) {
		vcc_log("*** malloc() failed\n");

		return 1;
	}

	for (;;) {
		nfds_ready = poll(fds, nfds_total, -1);

		if (unlikely(nfds_ready == -1)) {
			perror("poll()");

			break;
		}

		if (fds[0].revents & POLLIN) {
			size = sizeof(struct sockaddr_in);

			if (unlikely(usr_fd = accept(fd, (struct sockaddr *) &usr_addr, &size)) < 0) {
				perror("accept()");

				continue;
			}

			vcc_new_connection(usr_fd, &usr_addr.sin_addr);

			if (next_fd >= POLLFDS) {
				for (next_fd = 1; fds[next_fd].fd == -1 && next_fd < POLLFDS; next_fd++) 
					;

				if (next_fd == POLLFDS) 
					vcc_log("*** connection queue full. \n");	
			}

			fds[next_fd].fd = usr_fd;
			fds[next_fd].events = POLLIN;

			next_fd++;
			nfds_total++;
		}

		for (i = 1; i < nfds_total; i++) {
			if (fds[i].fd == -1) 
				continue;

			if (fds[i].revents & POLLIN) {
				size = read_until(fds[i].fd, buf, sizeof(struct vcc_relay_header));
				
				if (!size) {
					vcc_close_connection(fds[i].fd);

					close(fds[i].fd);
					fds[i].fd = -1;

					continue;
				}

				if (ntohl(((struct vcc_relay_header *) buf)->magic) == VCC_MAGIC) {
					read_until(fds[i].fd, buf + sizeof(struct vcc_relay_header), 
							REQ_SIZE - sizeof(struct vcc_relay_header));

					vcc_new_msg(fds[i].fd, buf);
				}

				else 
					relay_new_msg(fds[i].fd, buf);

				fds[i].events = POLLOUT;
			}

			if (fds[i].revents & POLLOUT) {
				vcc_write_packages(fds[i].fd);

				fds[i].events = POLLIN;
			}

			fds[i].revents = 0;
		}
	}

	afree(buf, REQ_SIZE);
	afree(fds, POLLFDS * sizeof(struct pollfd));

	return 0;
}

